---
title: Styling and Theming
description: Using, customizing, and extending the Voice UI Kit styles
icon: "SwatchBook"
---

The Voice UI Kit ships with a minimal default theme that’s meant to be customized and extended. It’s not a “creative design library” with strong opinions; it’s a flexible foundation for building your own voice-first UIs.

Under the hood it uses [Tailwind CSS](https://tailwindcss.com/) v4 and is designed to play nicely with your existing styles or Tailwind setup.

- `@pipecat-ai/voice-ui-kit/styles` - The default theme
- `@pipecat-ai/voice-ui-kit/styles.scoped` - The default theme, scoped to a `.vkui-root` class
- Dark mode support
- ShadUI compatibility (components registry coming soon!)
 - Optional utilities: `@pipecat-ai/voice-ui-kit/utilities` for helpful extras (see below)

## Setup

The Voice UI Kit CSS can be imported from the `@pipecat-ai/voice-ui-kit/styles` package.

```css title="global.css"
@import "@pipecat-ai/voice-ui-kit/styles";

:root {
  /* etc */
}
```

If you're using Tailwind in your project, you can import it alongside your own custom styles:

```css title="global.css"
@import "tailwindcss";
@import "tw-animate-css";

@import "@pipecat-ai/voice-ui-kit/styles";

:root {
  /* etc */
}
```

Alternatively, you can include the styles in your application code:

```tsx title="layout.tsx"
import "@pipecat-ai/voice-ui-kit/styles";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}
```

## Customization

Developers can approach customization in a few different ways:

- Passing custom styles or class names through to the components.
- Overriding CSS variables, such as colors, fonts, etc.
- Installing individual components from the registry and extending them (ejecting.)

<Accordions type="single">
  <Accordion title="Passing custom className">
    You can pass a custom className to the component to override the default styles.
    
    ```tsx
    import { Button } from "@pipecat-ai/voice-ui-kit";
    
    // Override with Tailwind classes
    <Button 
      className="bg-blue-500 hover:bg-blue-600 text-white border-0" 
      variant="primary"
    >
      Custom Button
    </Button>
    ```
  </Accordion>
  
  <Accordion title="Overriding CSS variables">
    You can override the CSS variables to customize the component's appearance globally or locally.
    
    ```css
    /* Global overrides in your CSS */
    :root {
      --color-primary: #3b82f6;
      --color-primary-foreground: #ffffff;
      --button-size-lg: 3rem;
      --shadow-short: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
    }
    
    /* Dark mode specific overrides */
    .dark {
      --color-primary: #60a5fa;
      --color-background: #0f172a;
    }
    
    /* Component-specific overrides */
    .my-theme {
      --color-active: #10b981;
      --color-active-foreground: #ffffff;
    }
    ```
    
    ```tsx
    // Apply theme to specific components
    <div className="my-theme">
      <Button variant="active">Success Action</Button>
    </div>
    ```
  </Accordion>
  
  <Accordion title="Installing components from the registry">
    Work in progress. This will allow you to install individual components and extend them directly in your codebase.
    
    ```bash
    # Future command (not yet available)
    npx @pipecat-ai/voice-ui-kit add button
    ```
    
    ```tsx
    // Future usage - direct component extension
    // import { Button } from "./components/ui/button";
    // 
    // export function CustomButton({ className, ...props }) {
    //   return (
    //     <Button 
    //       className={cn("my-custom-styles", className)} 
    //       {...props} 
    //     />
    //   );
    // }
    ```
  </Accordion>

</Accordions>

## Theme Provider and useTheme

Wrap your app with `ThemeProvider` to enable light/dark (or custom) themes and persistence.

```tsx title="layout.tsx"
import { ThemeProvider } from "@pipecat-ai/voice-ui-kit";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider defaultTheme="system" storageKey="voice-ui-kit-theme">
      {children}
    </ThemeProvider>
  );
}
```

#### Props

<TypeTable
  className="text-sm"
  type={{
    children: {
      description: "Application children to render",
      type: "React.ReactNode",
      required: true,
    },
    defaultTheme: {
      description: "Initial theme: 'system' or a custom string (e.g. 'dark', 'light', 'terminal')",
      type: '"system" | string',
      required: false,
      default: '"system"',
    },
    storageKey: {
      description: "Local storage key used to persist theme when not 'system'",
      type: "string",
      required: false,
      default: '"voice-ui-kit-theme"',
    },
    disableStorage: {
      description: "Disable localStorage persistence",
      type: "boolean",
      required: false,
      default: "false",
    },
  }}
/>

### useTheme hook

Use `useTheme` anywhere under the provider to read and set the theme.

```tsx
import { useTheme } from "@pipecat-ai/voice-ui-kit";

export function ThemeToggle() {
  const { theme, resolvedTheme, setTheme } = useTheme();
  return (
    <div>
      <div>theme: {theme}</div>
      <div>resolved: {resolvedTheme}</div>
      <button onClick={() => setTheme("light")}>Light</button>
      <button onClick={() => setTheme("dark")}>Dark</button>
      <button onClick={() => setTheme("system")}>System</button>
    </div>
  );
}
```

### Create a custom theme 

```css title="my-theme.css"
@custom-variant mytheme (&:is(.mytheme *));

@theme {
  /* Optional: local keyframes, shadows, etc. */
}

.mytheme {
  --font-sans: system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  --color-background: #000;
  --color-foreground: #e5e7eb;
  --color-primary: #22c55e;
  --color-secondary: #14532d;
  --color-border: #22c55e;
  --color-input: #0f172a;
  --color-accent: color-mix(in srgb, var(--color-primary) 10%, transparent);
  --color-accent-foreground: var(--color-foreground);
}
```

Apply it by toggling a class on a root container (or `html` if you prefer global):

```tsx
import "@pipecat-ai/voice-ui-kit/styles";
import "./my-theme.css";

export function App() {
  return (
    <div className="mytheme">
      {/* app */}
    </div>
  );
}
```

### Switching themes at runtime

You can switch themes programmatically either globally (ThemeProvider on `html`) or scoped to a container.

<Tabs items={["Using ThemeProvider", "Scoped (.vkui-root)"]}>
  <Tab value="Using ThemeProvider">

```tsx
import { ThemeProvider, useTheme } from "@pipecat-ai/voice-ui-kit";

export default function App() {
  return (
    <ThemeProvider defaultTheme="system">
      <Toolbar />
      <YourApp />
    </ThemeProvider>
  );
}

function Toolbar() {
  const { theme, resolvedTheme, setTheme } = useTheme();
  return (
    <div>
      <span>current: {resolvedTheme}</span>
      <button onClick={() => setTheme("light")}>Light</button>
      <button onClick={() => setTheme("dark")}>Dark</button>
      <button onClick={() => setTheme("system")}>System</button>
    </div>
  );
}
```

  </Tab>
  <Tab value="Scoped (.vkui-root)">

```tsx
import "@pipecat-ai/voice-ui-kit/styles.scoped";
import { ThemeProvider, useTheme } from "@pipecat-ai/voice-ui-kit";

export default function App() {
  return (
    <ThemeProvider defaultTheme="dark">
      <ScopedRoot />
    </ThemeProvider>
  );
}

function ScopedRoot() {
  const { resolvedTheme } = useTheme();
  // IMPORTANT: when using scoped styles, put the theme class on `.vkui-root`
  return (
    <div className={`vkui-root ${resolvedTheme}`}>
      <YourApp />
    </div>
  );
}
```

  </Tab>
</Tabs>

<Callout>
If you're using the scoped import (`@pipecat-ai/voice-ui-kit/styles.scoped`), set the theme class on the `.vkui-root` element instead of `html`.
</Callout>

## Themes

The default theme is intentionally light-touch. If you want a quicker start, we also provide optional themes you can use as-is or adapt.

```css title="global.css"
@import "@pipecat-ai/voice-ui-kit/styles"; 
@import "@pipecat-ai/voice-ui-kit/themes/terminal";
```

<Callout>Note: You must import the Voice UI Kit styles before importing a theme.</Callout>

Theme files are primarily CSS variable overrides.

## Utilities CSS

You can optionally import the utilities bundle for extra helpers used by the UI Kit.

```css title="global.css"
@import "tailwindcss";
@import "@pipecat-ai/voice-ui-kit/styles";
@import "@pipecat-ai/voice-ui-kit/utilities";
```

Why import `utilities`?
- Provides additional design tokens and helpers (e.g., scanlines, grid/stripe backgrounds, sizing shortcuts) used by components and examples
- Ensures custom compositions with UI Kit components merge correctly without conflicts
- Reduces duplication of one-off utility CSS in your app

## Scoping

The Voice UI Kit components are intended to be placed in a developer's own application, inheriting from their own styles or Tailwind theme.

Sometimes, however, it's preferable to isolate styles from your own. The Voice UI Kit bundle includes a fully scoped CSS file that can be used instead:

```css title="global.css"
@import "@pipecat-ai/voice-ui-kit/styles.scoped";

/* Your application CSS file */
```

The Voice UI Kit theme will now only apply when wrapped in a `.vkui-root` class, and declarations from your own Tailwind theme will not be applied.

```html
<div class="vkui-root">
  <span class="vkui:flex">✅ Scoped correctly</span>
</div>

<div>
  <span class="vkui:flex">❌ Not scoped</span>
</div>
```

## Dark mode

The Voice UI Kit theme supports dark mode using a `:root.dark` selector:

```html
<html class="dark">
  <body>
    ...
  </body>
</html>
```


## Class merging

The Voice UI Kit uses custom design tokens and utilities that aren't part of standard Tailwind CSS:

- **Custom shadow classes**: `shadow-xshort`, `shadow-short`, `shadow-long`, `shadow-xlong`
- **Custom button sizing**: `button-sm`, `button-md`, `button-lg`, `button-xl` and their icon variants
- **Custom spacing tokens**: `element-xs`, `element-sm`, `element-md`, `element-lg`, `element-xl`, `element-2xl`

These utilities support consistency across components and primitives, as well as reduce the amount of inline classes required.

Without proper merge handling, these custom classes would conflict with each other or not merge correctly when combined with standard Tailwind classes.
 

### Usage

The `cn` utility function is used throughout the Voice UI Kit components to merge class names intelligently:

```tsx
import { cn } from "@pipecat-ai/voice-ui-kit";

// This will properly merge shadow classes
<Card className={cn("shadow-short", className)} />

// This will properly merge button sizing
<Button className={cn("button-lg", customClasses)} />
```

This ensures that when developers pass custom classes to components, they merge correctly with the Voice UI Kit's design system without conflicts.

## Variable list

See available variables in the [source index.css](https://github.com/pipecat-ai/voice-ui-kit/blob/main/package/src/css/index.css).
